package com.shudes.util;

import java.math.*;
import java.text.*;
import java.util.*;

public class Money {
	private BigDecimal money; 

	public static final Compare COMPARATOR = new Compare(); 

	/** see BigDecimal for a description of the rounding rules */
	private final int ROUNDING_RULE = BigDecimal.ROUND_HALF_EVEN; 
	private final int SCALE = 8; 

	/**
	 * Creates a money object from a big decimal. Used internally.
	 */
	private Money(BigDecimal oDecimal) 
	{
		money = (oDecimal==null) ? new BigDecimal("0"):new BigDecimal(oDecimal.toString());
		money = money.setScale(SCALE,ROUNDING_RULE);
	}

	/**
	 * Creates a money object from a string representation of a money value.
	 * The constructor delegates parsing the string to BigDecimal.
	 * 
	 * @param stMoney
	 * @throws NumberFormatException
	 */

	public Money(String stMoney) throws NumberFormatException
	{
		money = (stMoney==null) ? new BigDecimal(0) : new BigDecimal(stMoney); 
		money = money.setScale(SCALE, ROUNDING_RULE); 
	}

	/**
	 * Creates a money from a double. Note that only SCALE digits are
	 * maintained.
	 * 
	 * @param doMoney
	 */
	public Money(Double doMoney) 
	{
		money = (doMoney==null) ? new BigDecimal("0") : new BigDecimal(doMoney.doubleValue()); 
		money = money.setScale(SCALE, ROUNDING_RULE); 
	}

	/**
	 * Creates a money from a double.
	 * 
	 * @param dMoney
	 */
	public Money(double dMoney) 
	{
		money = new BigDecimal(dMoney); 
		money = money.setScale(SCALE, ROUNDING_RULE); 
	}


	/**
	 * Adds this money object to oMoney and returns the result as a new
	 * Money object.
	 * 
	 * @param oMoney
	 * @return
	 */
	public Money add(Money oMoney) 
	{
		BigDecimal oAddAmt=null;
		if(oMoney!=null) 
			oAddAmt = money.add(oMoney.asBigDecimal()); 
		return new Money(oAddAmt); 
	}

	/**
	 * Subtracts oMoney from this money and returns the result.
	 * 
	 * @param oMoney
	 * @return
	 */
	public Money subtract(Money oMoney) 
	{
		BigDecimal oAmt=null;
		if(oMoney!=null) 
			oAmt = money.subtract(oMoney.asBigDecimal()); 
		return new Money(oAmt);
	}

	/**
	 * Multiplies <code>oRate</code> and this money and returns the
	 * result.
	 * 
	 * @param oRate
	 * @return
	 */
	public Money multiply(Money oRate) 
	{
		BigDecimal oAmt=null;
		if(oRate!=null) 
			oAmt = money.multiply(oRate.asBigDecimal()); 
		return new Money(oAmt);
	}

	/**
	 * Divides oRate into this money and returns the result.
	 * 
	 */
	public Money divide(Money oRate) 
	{
		BigDecimal oAmt=null;
		if(oRate!=null) 
			oAmt = money.divide(oRate.asBigDecimal(), ROUNDING_RULE); 
		return new Money(oAmt);
	}

	/**
	 * @return Returns the negative of this.
	 */
	public Money negate()
	{
		Money oAmt=null;
		if(money!=null)
			oAmt = multiply(new Money(-1.0));
		return oAmt;
	}

	/**
	 * Rounds this Money to whole cents.
	 */
	public Money round ()
	{
		return new Money(money.setScale (2, ROUNDING_RULE).setScale (SCALE, ROUNDING_RULE));
	}


	// the toString() (noarg) method must be an unformatting string
	// representation that will work for INSERTs into the db
	// (i.e. no commas and no $'s

	@Override
	public String toString() 
	{
		return toString(2,false,true);
	}

	public Double toDouble() 
	{
		return new Double(money.doubleValue()); 
	}

	public double doubleValue() 
	{
		return money.doubleValue(); 
	}

	public String toString(int iDecimalPlaces, boolean bShowDollarSign, boolean bShowCommas) 
	{
//		String st=Scalars.double2money(this.round().doubleValue());
		NumberFormat oFormat;
		String stFormat="";

		if(bShowDollarSign)
			stFormat += "$";

		if(bShowCommas)
			stFormat += "#,##0";
		else
			stFormat += "##0";

		if(iDecimalPlaces>0)
		{
			stFormat += "."; 

			for(int i=0; i<iDecimalPlaces; i++)
				stFormat += "0";
		}

		oFormat = new DecimalFormat(stFormat);
		return oFormat.format(this.doubleValue());
	}

	@Override
	public int hashCode() 
	{
		return (money==null) ? 0 :money.hashCode();
	}

	@Override
	public boolean equals(Object o1) 
	{
		return (o1!=null) && (o1 instanceof Money) && toString().equals(((Money) o1).toString()); 
	}

	public BigDecimal asBigDecimal() 
	{
		return  money;
	}

	public static Money zero() 
	{
		return new Money("0.00"); 
	}

	public static Money zeroForNull(Money oMoney)
	{
		if(oMoney == null)
			return Money.zero();
		return oMoney;
	}

	/**
	 * Gives you zero for negative or null Money otherwise returns same
	 * value
	 * 
	 * @param oMoney
	 * @return Non Null Non Negative Money.
	 */
	public static Money zeroForNegative(Money oMoney)
	{
		Money oNullSafeMoney = zeroForNull(oMoney);
		if(Money.isLessOrEqual(oNullSafeMoney,Money.zero()))
			return Money.zero();
		return oNullSafeMoney;
	}

	public static Money create(Double doValue) 
	{
		return (doValue==null) ? null : new Money(doValue); 
	}

	public static Double toDouble(Money oMoney)
	{
		return (oMoney==null) ? null : oMoney.toDouble(); 
	}

	public static String toString(Money oMoney) 
	{
		return (oMoney==null) ? null : oMoney.toString(); 
	}

	public static String toHtml(Money oMoney) 
	{
		return (oMoney==null) ? "&nbsp;" : oMoney.toString(); 
	}


	public static boolean isEqual(Money m1, Money m2) 
	{
		return COMPARATOR.compare(m1, m2)==0; 
	}

	public static boolean isGreater(Money m1, Money m2) 
	{
		return COMPARATOR.compare(m1,m2) > 0; 
	}

	public static boolean isGreaterOrEqual(Money m1, Money m2) 
	{
		return COMPARATOR.compare(m1,m2) >= 0; 
	}

	public static boolean isLess(Money m1, Money m2) 
	{
		return COMPARATOR.compare(m1,m2) < 0; 
	}

	public static boolean isLessOrEqual(Money m1, Money m2) 
	{
		return COMPARATOR.compare(m1,m2) <= 0; 
	}

	public static class Compare implements Comparator
	{
		public int compare(Object o1, Object o2) 
		{
			Money m1 = (Money) o1; 
			Money m2 = (Money) o2; 

			if(m1==null) 
			{
				return (m2==null) ? 0 : -1; 
			} 

				if(m2==null) 
					return 1; 

				if(m1.doubleValue()==m2.doubleValue())
					return 0; 

				return (m1.doubleValue() < m2.doubleValue()) ? -1 : 1; 

		}
	}

}


